Skip to main content

pacenotes_installer\wizard\scene/
summary.rs

1use windows_sys::Win32::Foundation::{HWND, LPARAM, WPARAM};
2use windows_sys::Win32::UI::Controls as ctl;
3use windows_sys::Win32::UI::WindowsAndMessaging as wm;
4
5/// Scene 7: what the run did. Finish is the only way out.
6pub unsafe extern "system" fn summary_page(
7    dialog: HWND,
8    message: u32,
9    _wparam: WPARAM,
10    lparam: LPARAM,
11) -> isize {
12    match message {
13        wm::WM_INITDIALOG => {
14            crate::wizard::page::adopt_state(dialog, lparam);
15            1
16        }
17        wm::WM_NOTIFY => match crate::wizard::page::notify_code(lparam) {
18            Some(ctl::PSN_SETACTIVE) => {
19                crate::wizard::page::set_sheet_caption(dialog);
20                crate::wizard::page::set_header(dialog, crate::i18n::IDS_S7_TITLE);
21                crate::wizard::page::set_buttons(dialog, ctl::PSWIZB_FINISH);
22                crate::wizard::page::set_next_text(dialog, crate::i18n::IDS_BTN_FINISH);
23                crate::ui::enable(
24                    crate::wizard::page::sheet(dialog),
25                    crate::ui::IDCANCEL,
26                    false,
27                );
28                if let Some(state) = crate::wizard::page::state(dialog) {
29                    show_summary(dialog, state);
30                }
31                crate::wizard::page::reply(dialog, 0)
32            }
33            _ => 0,
34        },
35        _ => 0,
36    }
37}
38
39// DESIGN.md: the log collapses on success and opens on failure, or when a
40/// package could not be downloaded, and a collapsed log's room goes to the
41/// detail block.
42unsafe fn show_summary(dialog: HWND, state: &crate::wizard::state::WizardState) {
43    crate::ui::set_text_raw(
44        dialog,
45        crate::wizard::control_ids::IDC_S7_RESULT,
46        &result_line(state),
47    );
48    crate::ui::set_text_raw(
49        dialog,
50        crate::wizard::control_ids::IDC_S7_DETAIL,
51        &detail_lines(state).join("\r\n"),
52    );
53
54    let log = crate::wizard::control_ids::IDC_S7_LOG;
55    let failed = matches!(
56        state.outcome,
57        Some(crate::worker::Outcome::Failed(_)) | None
58    ) || !state.not_downloaded.is_empty();
59    crate::ui::set_text_raw(dialog, log, &state.log.text());
60    crate::ui::scroll_to_end(dialog, log);
61    crate::ui::show(dialog, log, failed);
62    if !failed {
63        crate::ui::stretch_to_bottom_of(dialog, crate::wizard::control_ids::IDC_S7_DETAIL, log);
64    }
65}
66
67/// A restore says what it went back to, since "it worked" does not answer
68/// "did I get yesterday back".
69fn result_line(state: &crate::wizard::state::WizardState) -> String {
70    match state.outcome.as_ref() {
71        Some(crate::worker::Outcome::Done { .. })
72            if state.route == crate::wizard::state::Route::Restore =>
73        {
74            let when = state
75                .restore_to
76                .as_ref()
77                .map(|point| pacenotes_installer::restore::human_time(&point.name))
78                .unwrap_or_default();
79            crate::i18n::load_string_arg(crate::i18n::IDS_S7_RESTORED, &when)
80        }
81        Some(crate::worker::Outcome::Done { .. }) => {
82            crate::i18n::load_string(crate::i18n::IDS_S7_OK)
83        }
84        Some(crate::worker::Outcome::Failed(message)) => {
85            crate::i18n::load_string_arg(crate::i18n::IDS_S7_FAILED, message)
86        }
87        Some(crate::worker::Outcome::Cancelled) | None => {
88            crate::i18n::load_string(crate::i18n::IDS_S7_CANCELLED)
89        }
90    }
91}
92
93fn detail_lines(state: &crate::wizard::state::WizardState) -> Vec<String> {
94    let mut lines: Vec<String> = Vec::new();
95    if let Some(game) = state.game {
96        lines.push(crate::i18n::load_string_arg(
97            crate::i18n::IDS_S7_GAME,
98            &format!("{} — {}", game.title(), state.folder),
99        ));
100    }
101    for list in &state.config.lists {
102        if let Some(option) = list.selected.and_then(|index| list.options.get(index)) {
103            let caption =
104                crate::i18n::load_string(super::configuration::category_caption(list.category));
105            lines.push(format!("{caption} {}", option.label));
106        }
107    }
108    if !state.config.extras.is_empty() {
109        let leaves: Vec<&str> = state
110            .config
111            .extras
112            .iter()
113            .map(|key| key.rsplit('/').next().unwrap_or(key))
114            .collect();
115        lines.push(crate::i18n::load_string_arg(
116            crate::i18n::IDS_S7_EXTRAS,
117            &leaves.join(", "),
118        ));
119    }
120    // From the state, since the transfers happened screens ago, or not at all on the Configure route.
121    lines.push(if state.downloaded > 0 {
122        crate::i18n::load_string_arg(
123            crate::i18n::IDS_S7_DOWNLOADS,
124            &format!(
125                "{} ({})",
126                state.downloaded,
127                pacenotes_installer::size::human(state.downloaded_bytes)
128            ),
129        )
130    } else {
131        crate::i18n::load_string(crate::i18n::IDS_S7_NOTHING_TODO)
132    });
133    if !state.not_downloaded.is_empty() {
134        let labels: Vec<&str> = state
135            .not_downloaded
136            .iter()
137            .map(|failure| failure.label.as_str())
138            .collect();
139        lines.push(crate::i18n::load_string_arg(
140            crate::i18n::IDS_S7_NOT_DOWNLOADED,
141            &labels.join(", "),
142        ));
143    }
144    if let Some(crate::worker::Outcome::Done { applied, .. }) = &state.outcome {
145        lines.extend(applied_lines(applied));
146    }
147    if let Some(path) = state.log.path() {
148        lines.push(crate::i18n::load_string_arg(
149            crate::i18n::IDS_S7_LOGFILE,
150            &path.display().to_string(),
151        ));
152    }
153    lines
154}
155
156/// What changed in the game folder, including the files left alone, so a
157/// user's own edits surviving does not read as the install failing.
158fn applied_lines(applied: &crate::worker::Applied) -> Vec<String> {
159    let mut lines = vec![crate::i18n::load_string_arg(
160        crate::i18n::IDS_S7_CHANGED,
161        &format!(
162            "{} / {}",
163            applied.installed.len(),
164            applied.uninstalled.len()
165        ),
166    )];
167    if !applied.left_alone.is_empty() {
168        lines.push(crate::i18n::load_string_arg(
169            crate::i18n::IDS_S7_LEFT_ALONE,
170            &applied.left_alone.join(", "),
171        ));
172    }
173    if let Some(point) = &applied.restore_point {
174        lines.push(crate::i18n::load_string_arg(
175            crate::i18n::IDS_S7_RESTORE_POINT,
176            point,
177        ));
178    }
179    lines
180}